﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ButsaLib;
using ButsaLib.Models;
using Ninject;

namespace ButsaForecast.SpecialTypes
{
	class DxAlgorithm
	{
		readonly double _minTeamDiffSize = 0.8;

		private readonly int _intervalsCount;

		public EventHandler AnalyzisCompleted;

		public List<IntervalStat> IntervalStats = null;

		private IKernel _kernel;

		private string _connectionString;

		/// <summary>
		/// Конструктор алгоритма.
		/// </summary>
		/// <param name="kernel"> </param>
		/// <param name="x">Количество интервалов разбиения.</param>
		/// <param name="connectionString"> </param>
		/// <param name="minTeamDiffSize"></param>
		public DxAlgorithm(IKernel kernel, int x, string connectionString, double minTeamDiffSize = 0.1)
		{
			_kernel = kernel;
			_intervalsCount = x;
			_minTeamDiffSize = minTeamDiffSize;
			_connectionString = connectionString;
		}

		public void StartAnalyzis()
		{
			Parallel.Invoke(() => 
			{
				StringBuilder message = new StringBuilder();

				List<IDxPairCalculation> pairCalculations = PrepareDxPairs();

				IntervalStat[] intervals = CalculateIntervals(pairCalculations);

				IntervalStats = new List<IntervalStat>(intervals);

				if (AnalyzisCompleted != null)
					AnalyzisCompleted(this, new EventArgs());
			});
		}

		public List<ForecastPair> MakeForecast(ForecastSnapshot snapshot)
		{
			ButsaDbContext butsaDbContext = new ButsaDbContext(_connectionString);

			//сортируем этапы по эффективности
			var orderedIntervals = IntervalStats.OrderByDescending(item => item.Balls / (item.EndPosition - item.StartPosition) * 3).ToList();

			List<ForecastPair> forecastPairs = butsaDbContext.ForecastPairs
				.Where(item => item.ForecastSnapshot.ForecastSnapshotId == snapshot.ForecastSnapshotId)
				.Include("HomeTeamShot")
				.Include("GuestTeamShot")
				.Include("HomeTeamShot.PlayerShots")
				.Include("GuestTeamShot.PlayerShots")
				.ToList();
			
			List<IDxPairCalculation> dxPairs = new List<IDxPairCalculation>(forecastPairs.Count);
			foreach (var forecastPair in forecastPairs)
			{
				IDxPairCalculation dxPairCalculation = _kernel.Get<IDxPairCalculation>();
				dxPairCalculation.ForecastPair = forecastPair;
				dxPairCalculation.HomeTeamName = forecastPair.HomeTeamShot.TeamName;
				dxPairCalculation.GuestTeamName = forecastPair.GuestTeamShot.TeamName;
				dxPairCalculation.HomeGoals = forecastPair.HomeTeamGoals;
				dxPairCalculation.GuestGoals = forecastPair.GuestTeamGoals;
				dxPairs.Add(dxPairCalculation);
			}

			List<ForecastPair> forecastedPairs = new List<ForecastPair>();
			int leavedForecastsCount = 0;
			int z = 0;
			while (leavedForecastsCount < 16)
			{
				var interval = orderedIntervals[z];

				foreach (var dxPair in dxPairs
					.Where(item => item.GetTDV() < interval.Start && item.GetTDV() > interval.End && item.Recomendation)
					.Take(16 - leavedForecastsCount))
				{
					var fp = forecastPairs.First(item => item.ForecastPairId == dxPair.ForecastPairId);
					//fp.HomeTeamGoalsForecast = interval.Hg;
					//fp.GuestTeamGoalsForecast = interval.Gg;
					forecastedPairs.Add(fp);
				}
				leavedForecastsCount += dxPairs
					.Count(item => item.GetTDV() < interval.Start && item.GetTDV() > interval.End && item.Recomendation);
				z++;
			}
			return forecastedPairs;
		}

		private IntervalStat[] CalculateIntervals(List<IDxPairCalculation> pairCalculations)
		{
			//подбор участков для прогноза
			//размер участка не менее minTeamDiffSize (%) от общего диапазона
			pairCalculations = pairCalculations
				.OrderByDescending(item => item.GetTDV())
				.ToList();

			double minValue = pairCalculations[pairCalculations.Count - 1].GetTDV();
			double maxValue = pairCalculations[0].GetTDV();
			double minSizeValue = (maxValue - minValue) * _minTeamDiffSize;

			//подвижные границы
			int[] boundaries = new int[_intervalsCount - 1];
			boundaries[0] = 0;
			while (boundaries[0] < pairCalculations.Count &&
				pairCalculations[0].GetTDV() - pairCalculations[boundaries[0]].GetTDV() < minSizeValue)
				boundaries[0]++;
			for (int i = 1; i < boundaries.Length; i++)
			{
				boundaries[i] = boundaries[i - 1] + 2;
				while (boundaries[i] < pairCalculations.Count &&
					pairCalculations[boundaries[i - 1]].GetTDV() - pairCalculations[boundaries[i]].GetTDV() < minSizeValue)
					boundaries[i]++;
			}
			//контрольные границы
			int leftBoundary = boundaries[0];
			int rightBoundary = pairCalculations.Count - 3;
			while (rightBoundary > 0 && pairCalculations[rightBoundary].GetTDV() -
				pairCalculations[pairCalculations.Count - 1].GetTDV() < minSizeValue)
				rightBoundary--;

			//начальное разбиение, которое затем будет улучшаться
			IntervalStat[] intervals = new IntervalStat[_intervalsCount];
			intervals[0] = AnanlyzeInterval(pairCalculations, 0, boundaries[0]);
			for (int i = 1; i < _intervalsCount - 1; i++)
			{
				intervals[i] = AnanlyzeInterval(pairCalculations, boundaries[i - 1] + 1, boundaries[i]);
			}
			intervals[_intervalsCount - 1] = AnanlyzeInterval(pairCalculations, boundaries[_intervalsCount - 2] + 1, pairCalculations.Count - 1);

			IntervalsAdjustments(boundaries, intervals, pairCalculations, rightBoundary, minSizeValue);

			foreach (var intervalStat in intervals)
			{
				intervalStat.Start = pairCalculations[intervalStat.StartPosition].GetTDV();
				intervalStat.End = pairCalculations[intervalStat.EndPosition].GetTDV();
			}

			return intervals;
		}

		private List<IDxPairCalculation> PrepareDxPairs()
		{
			ButsaDbContext butsaDbContext = new ButsaDbContext(_connectionString);

			var forecastPairs = butsaDbContext.ForecastPairs
				.Include("ForecastSnapshot")
				.Where(item => item.ForecastSnapshot.ResultsApplied)
				.Include("HomeTeamShot")
				.Include("GuestTeamShot")
				.Include("HomeTeamShot.PlayerShots")
				.Include("GuestTeamShot.PlayerShots")
				.ToList();

			List<IDxPairCalculation> pairCalculations = new List<IDxPairCalculation>(forecastPairs.Count);
			foreach (var forecastPair in forecastPairs)
			{
				IDxPairCalculation dxPairCalculation = _kernel.Get<IDxPairCalculation>();
				dxPairCalculation.ForecastPair = forecastPair;
				dxPairCalculation.HomeTeamName = forecastPair.HomeTeamShot.TeamName;
				dxPairCalculation.GuestTeamName = forecastPair.GuestTeamShot.TeamName;
				dxPairCalculation.HomeGoals = forecastPair.HomeTeamGoals;
				dxPairCalculation.GuestGoals = forecastPair.GuestTeamGoals;
				pairCalculations.Add(dxPairCalculation);
			}
			return pairCalculations;
		}

		/// <summary>
		/// Поиск наиболее часто встречающейся разницы мячей и счета, рассчета кол-ва баллов.
		/// </summary>
		/// <param name="array"></param>
		/// <param name="s"></param>
		/// <param name="e"></param>
		/// <returns></returns>
		private static IntervalStat AnanlyzeInterval(List<IDxPairCalculation> array, int s, int e)
		{
			IntervalStat intervalStat = new IntervalStat { StartPosition = s, EndPosition = e };

			IDxPairCalculation[] tmp = array.Skip(s).Take(e - s + 1).ToArray();

			//вычисление наиболее частой разницы mostFrequentDiff
			//разница - кол-во появлений
			Dictionary<int, int> mostFrequentDiffs = new Dictionary<int, int>();
			foreach (var dPoint in tmp)
			{
				if (mostFrequentDiffs.ContainsKey(dPoint.HomeGoals - dPoint.GuestGoals))
					mostFrequentDiffs[dPoint.HomeGoals - dPoint.GuestGoals] = mostFrequentDiffs[dPoint.HomeGoals - dPoint.GuestGoals] + 1;
				else
					mostFrequentDiffs.Add(dPoint.HomeGoals - dPoint.GuestGoals, 1);
			}
			var keyValuePairsForDiffs = mostFrequentDiffs.ToList();
			var mostFrequentDiff = keyValuePairsForDiffs[0];
			for (int i = 1; i < keyValuePairsForDiffs.Count; i++)
			{
				if (mostFrequentDiff.Value < keyValuePairsForDiffs[i].Value)
					mostFrequentDiff = keyValuePairsForDiffs[i];
			}

			//вычисление наиболее частого кол-ва голов домашней команды
			//hg - кол-во появлений
			Dictionary<int, int> mostFrequentHgs = new Dictionary<int, int>();
			foreach (var dPoint in tmp.Where(item => item.HomeGoals - item.GuestGoals == mostFrequentDiff.Key))
			{
				if (mostFrequentHgs.ContainsKey(dPoint.HomeGoals))
					mostFrequentHgs[dPoint.HomeGoals] = mostFrequentHgs[dPoint.HomeGoals] + 1;
				else
					mostFrequentHgs.Add(dPoint.HomeGoals, 1);
			}
			var keyValuePairsForHg = mostFrequentHgs.ToList();
			var mostFrequentHg = keyValuePairsForHg[0];
			for (int i = 1; i < keyValuePairsForHg.Count; i++)
			{
				if (mostFrequentHg.Value < keyValuePairsForHg[i].Value)
					mostFrequentHg = keyValuePairsForHg[i];
			}

			intervalStat.Hg = mostFrequentHg.Key;
			intervalStat.Gg = intervalStat.Hg - mostFrequentDiff.Key;

			//рассчет баллов
			intervalStat.Balls = 0;
			foreach (var dPoint in tmp)
			{
				if (dPoint.HomeGoals == intervalStat.Hg && dPoint.GuestGoals == intervalStat.Gg)
					intervalStat.Balls += 3;
				else if (dPoint.GuestGoals - dPoint.HomeGoals == intervalStat.Gg - intervalStat.Hg)
					intervalStat.Balls += 2;
				else if ((dPoint.HomeGoals > dPoint.GuestGoals && intervalStat.Hg > intervalStat.Gg) ||
					(dPoint.HomeGoals < dPoint.GuestGoals && intervalStat.Hg < intervalStat.Gg))
					intervalStat.Balls += 1;
			}

			return intervalStat;
		}

		private void IntervalsAdjustments(int[] boundaries, IntervalStat[] intervals, List<IDxPairCalculation> pairCalculations,
					int rightBoundary, double minSizeValue)
		{

			while (true)
			{
				//перебираем последний интервал
				while (boundaries[boundaries.Length - 1] <= rightBoundary)
				{
					//анализ текущего разбиения
					IntervalStat[] tmpIntervals = new IntervalStat[intervals.Length];
					tmpIntervals[0] = AnanlyzeInterval(pairCalculations, 0, boundaries[0]);
					for (int i = 1; i < tmpIntervals.Length - 1; i++)
					{
						tmpIntervals[i] = AnanlyzeInterval(pairCalculations, boundaries[i - 1] + 1, boundaries[i]);
					}
					tmpIntervals[tmpIntervals.Length - 1] = AnanlyzeInterval(pairCalculations, boundaries[tmpIntervals.Length - 2] + 1,
																			 pairCalculations.Count - 1);

					//корректировка текущего максимального разибения, если нужно
					if (tmpIntervals.Sum(item => item.Balls) > intervals.Sum(item => item.Balls))
					{
						for (int i = 0; i < tmpIntervals.Length; i++)
							intervals[i] = tmpIntervals[i];
					}

					boundaries[boundaries.Length - 1] += 1;
				}

				//подтягиваем предыдущий интервал на 1, устанавливаем последний на расстоянии 10% от него
				//если после этого нарушаются условия, то подтягиваем на 1 предпредыдущий интервал, устанавливаем последующие на 10% от него
				//и снова проверяем условия
				int dx = 2;
				bool check = false;
				do
				{
					boundaries[boundaries.Length - dx] += 1;

					//подтягиваем все последующие интервалы
					for (int i = boundaries.Length - dx; i < boundaries.Length - 1; i++)
					{
						boundaries[i + 1] = boundaries[i] + 2;
						while (boundaries[i + 1] <= rightBoundary &&
							   pairCalculations[boundaries[i]].GetTDV() - pairCalculations[boundaries[i + 1]].GetTDV() < minSizeValue)
							boundaries[i + 1] += 1;

						//проверка, есть ли резерв для подтягивания значений
						if (boundaries[i + 1] > rightBoundary ||
							pairCalculations[boundaries[i]].GetTDV() - pairCalculations[boundaries[i + 1]].GetTDV() < minSizeValue)
						{
							check = true;
							break;
						}
					}
					dx++;
				} while (boundaries.Length - dx >= 0 && check);

				//не удалось подвинуть интервалы - завершаем алгоритм
				if (boundaries.Length - dx == -1 && boundaries[boundaries.Length - 1] >= rightBoundary)
				{
					return;
				}
			}
		}
	}
}
